using System;
using System.Collections.Generic;
using System.IO;
using Cysharp.Threading.Tasks;
using UnityEditor;
using UnityEngine;
using UnityEngine.UI;

namespace CodeGenerator
{
    public static class GenerateCodeFactory
    {
        private static readonly Type[] Controls =
        {
            typeof(Text),
            typeof(Button),
            typeof(ScrollRect),
            typeof(Image),
            typeof(Toggle),
            typeof(Slider),
            typeof(Scrollbar),
            typeof(Dropdown),
            typeof(InputField),
        };

        [MenuItem("UI代码生成/生成代码")]
        public static void GenerateCode()
        {
            var activeObject = Selection.activeGameObject;
            var activeTransform = Selection.activeGameObject.transform;
            GetPrefabPath(activeObject);
            var name = GetName(activeTransform);
            var path = Path.Combine(Environment.CurrentDirectory, "Assets/");
            var @namespace = Path.GetFileName(Environment.CurrentDirectory);
            GenerateAsync(@namespace,
                name,
                ViewDesignTemplate.Template,
                $"{path}/{name}View.Design.cs",
                code =>
                {
                    code = code.Replace("%fieldDeclareList%", BindControls(activeTransform));
                    code = code.Replace("%fieldBinding%", FieldsBinding(activeTransform));
                    code = code.Replace("%PrefabPath%", GetPrefabPath(activeObject));
                    code = code.Replace("%RootPath%", "/Canvas");
                    return code;
                }).Forget();

            var viewPath = $"{path}/{name}View.cs";
            if (!File.Exists(viewPath))
                GenerateAsync(@namespace, name, ViewTemplate.Template, viewPath).Forget();

            var presenterPath = $"{path}/{name}Presenter.cs";
            if (!File.Exists(presenterPath))
                GenerateAsync(@namespace, name, PresenterTemplate.Template, presenterPath).Forget();

            Debug.Log("成功生成代码");
        }

        public static UniTask GenerateAsync(string @namespace, string name, string template, string outputPath,
            Func<string, string> extend = null)
        {
            var code = extend?.Invoke(template);
            if (code == null)
                code = template;
            code = code.Replace("%namespace%", @namespace);
            code = code.Replace("%ClassName%", name);

            File.WriteAllText(outputPath, code);

            return UniTask.CompletedTask;
        }

        private static string GetPrefabPath(GameObject gameObject)
        {
            var assetPath = AssetDatabase.GetAssetPath(gameObject);
            var path = assetPath.Replace("Assets/Resources/", "");
            return path.Replace(".prefab", "");
        }

        private static List<Transform> GetChildren(Transform transform)
        {
            var transforms = new List<Transform> { transform };
            var childTransforms = transform.GetComponentsInChildren<Transform>(true);
            foreach (var childTransform in childTransforms)
            {
                if (childTransform.parent == transform)
                    transforms.Add(childTransform);
            }

            return transforms;
        }

        private static string BindControls(Transform transform)
        {
            var childTransforms = GetChildren(transform);

            var fieldDeclareList = new List<string>();
            foreach (var childTransform in childTransforms)
            {
                var components = GetComponents(childTransform);
                foreach (var component in components)
                {
                    fieldDeclareList.Add($"private {component.GetType()} {GetFieldName(component)};");
                }
            }

            return string.Join("\n        ", fieldDeclareList);
        }

        private static string FieldsBinding(Transform transform)
        {
            var fieldBindingList = new List<string>();

            var childTransforms = GetChildren(transform);
            foreach (var childTransform in childTransforms)
            {
                fieldBindingList.Add(BindField(childTransform));
            }

            return string.Join("\n            ", fieldBindingList);
        }

        private static string BindField(Transform transform)
        {
            var components = GetComponents(transform);
            if (components.Count == 0)
                return string.Empty;

            var list = new List<string>();
            foreach (var component in components)
            {
                var path = GetPath(transform, "");
                string code;
                var type = component.GetType();
                if (string.IsNullOrEmpty(path))
                {
                    code = $"{GetFieldName(component)} = ViewGameObject.transform.GetComponent<{type}>();";
                }
                else
                {
                    code = $"{GetFieldName(component)} = ViewGameObject.transform.Find(\"{path}\").GetComponent<{type}>();";
                }

                list.Add(code);
            }

            return string.Join("\n            ", list);
        }

        private static List<Component> GetComponents(Transform transform)
        {
            var componentList = new List<Component>();
            foreach (var control in Controls)
            {
                var component = transform.GetComponent(control);
                if (component != null)
                    componentList.Add(component);
            }

            return componentList;
        }

        private static string GetPath(Transform transform, string path)
        {
            if (transform.parent == null)
                return path;

            if (string.IsNullOrEmpty(path))
                return GetPath(transform.parent, $"{transform.name}");
            return GetPath(transform.parent, $"{transform.name}/{path}");
        }

        private static string GetFieldName(Component component)
        {
            var name = component.gameObject.name.Replace(" ", "");
            return $"_{name}{component.GetType().Name}";
        }

        private static string GetFieldName(Transform transform)
        {
            return GetFieldName((Component)transform);
        }

        private static string GetName(Transform transform)
        {
            return transform.name.Replace(" ", "");
        }
    }
}